﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using SpectrumAnalyser.Config.Contract;
using SpectrumAnalyser.ModelObjects;

namespace SpectrumAnalyser.Config.Client
{
    public class ExceptionMarshallingMessageInspector : IClientMessageInspector
    {
        private const string DETAIL_ELEMENT_NAME = "Detail";

        #region IClientMessageInspector Members

        void IClientMessageInspector.AfterReceiveReply(ref Message reply, object correlationState)
        {
            if (reply.IsFault)
            {
                // Create a copy of the original reply to allow default processing of the message
                MessageBuffer buffer = reply.CreateBufferedCopy(Int32.MaxValue);
                Message copy = buffer.CreateMessage(); // Create a copy to work with
                reply = buffer.CreateMessage(); // Restore the original message

                object faultDetail = ReadFaultDetail(copy);
                var exception = faultDetail as Exception;
                if (exception != null)
                {
                    throw exception;
                }
            }
        }

        object IClientMessageInspector.BeforeSendRequest(ref Message request, IClientChannel channel)
        {
            return null;
        }

        #endregion

        private static object ReadFaultDetail(Message reply)
        {
            using (XmlDictionaryReader reader = reply.GetReaderAtBodyContents())
            {
                // Find <soap:Detail>
                while (reader.Read())
                {
                    if (reader.NodeType == XmlNodeType.Element &&
                        reader.LocalName.Equals(DETAIL_ELEMENT_NAME, StringComparison.InvariantCultureIgnoreCase))
                    {
                        break;
                    }
                }

                // Did we find it?
                if (reader.NodeType != XmlNodeType.Element ||
                    !reader.LocalName.Equals(DETAIL_ELEMENT_NAME, StringComparison.InvariantCultureIgnoreCase))
                {
                    return null;
                }

                // Move to the contents of <soap:Detail>
                if (!reader.Read())
                {
                    return null;
                }

                // Deserialize the fault
                var serializer = new NetDataContractSerializer();
                try
                {
                    return serializer.ReadObject(reader);
                }
                catch (FileNotFoundException)
                {
                    // Serializer was unable to find assembly where exception is defined 
                    return null;
                }
            }
        }
    }
    public class ExceptionMarshallingEndpointBehavior : IEndpointBehavior
    {
        #region IEndpointBehavior Members

        void IEndpointBehavior.AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection parameters)
        {
        }

        void IEndpointBehavior.ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime runtime)
        {
            this.ApplyClientBehavior(runtime);
        }

        void IEndpointBehavior.ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher dispatcher)
        {
        }

        void IEndpointBehavior.Validate(ServiceEndpoint endpoint)
        {
        }

        #endregion

        private void ApplyClientBehavior(ClientRuntime runtime)
        {
            // Don't add a message inspector if it already exists
            foreach (IClientMessageInspector messageInspector in runtime.MessageInspectors)
            {
                if (messageInspector is ExceptionMarshallingMessageInspector)
                {
                    return;
                }
            }

            runtime.MessageInspectors.Add(new ExceptionMarshallingMessageInspector());
        }
    }
    public class ConfigClientImpl : ClientBase<IConfigService>, IConfigService
    {
        public ConfigClientImpl(Binding binding, EndpointAddress remoteAddress) :
            base(binding, remoteAddress)
        {
            this.Endpoint.Behaviors.Add(new ExceptionMarshallingEndpointBehavior());
        }

        public void Ping()
        {
            base.Channel.Ping();
        }

        public IList<Instance> GetInstances()
        {
            return base.Channel.GetInstances();
        }

        public Instance GetInstanceByName(string instanceName)
        {
            return base.Channel.GetInstanceByName(instanceName);
        }
    }
}
